//
// Created by hujin on 2020/10/24.
//

#include "ast_exprs.h"
#include "ast_statements.h"

bool ExprNode::isReturnInt() {
    if(terms.size() > 1) return true;
    for(auto i : terms){
        if(i.second->isReturnInt()) return true;
    }
    return false;
}

int ExprNode::genCode(Synthesizer *out, MemoryAllocator *allocator, int var_index) {
    int reg;
    for(int i = 0; i < terms.size(); i++){
        if(i == 0){
            reg = terms[i].second->genCode(out, allocator, var_index);
            if(!terms[i].first){
                reg = allocator->loadToReg(var_index, true);
                out->text<<"neg $"<<reg<<" $"<<reg<<std::endl;
            }
        } else{
            int cnst = 0, var = 0;
            while(i < terms.size() && terms[i].second->returnConstant(var)){
                if(terms[i].first)cnst+= var;
                else cnst -= var;
                i++;
            }
            if(cnst != 0) {
                reg = allocator->loadToReg(var_index, true);
                out->text<<"addi $"<<reg<<" $"<<reg<<", "<<cnst<<std::endl;
            }
            if(i == terms.size())break;
            terms[i].second->genCode(out, allocator, tempIndex);
            int reg1 = allocator->loadToReg(tempIndex, false);
            reg = allocator->loadToReg(var_index, true);
            if(terms[i].first){
                out->text<<"addu $"<<reg<<" $"<<reg<<" $"<<reg1<<std::endl;
            }
            else {
                out->text<<"subu $"<<reg<<" $"<<reg<<" $"<<reg1<<std::endl;
            }
        }
    }
    allocator->releaseReg(tempIndex);
    return reg;
}

bool ExprNode::returnConstant(int &val) {
    if(this->calced)return cnstVal;
    calced = true;
    int ans = 0;
    for(auto x : terms){
        int p = 0;
        if(!x.second->returnConstant(p))return false;
        ans = x.first ? ans + p : ans - p;
    }
    cnstVal = val = ans;
    return true;
}

std::set<int> ExprNode::getUseVarIndex() {
    std::set<int> ret;
    for (auto x : terms) {
        const std::set<int> &st = x.second->getUseVarIndex();
        if(!st.empty())   ret.insert(st.begin(), st.end());
    }
    return ret;
}

bool TermNode::isReturnInt() {
    if(factors.size() > 1) return true;
    for(auto i : factors){
        if(i.second->isReturnInt_Factor()) return true;
    }
    return false;
}


//这里由于除法是整除，不可和乘法进行交换律
int TermNode::genCode(Synthesizer *out, MemoryAllocator *allocator, int var_index) {
    int reg;
    for(int i = 0; i < factors.size(); i++){
        if(i == 0){
            factors[i].second->genCode(out, allocator, var_index);
            reg = allocator->loadToReg(var_index, false);
            //int reg = allocator->loadToReg(var_index, true);
            if(!factors[i].first){
                log_error(4, "term with div first", line, pos);
                //out->text<<"neg $"<<reg<<" $"<<reg<<std::endl;
            }
        }  else{
            factors[i].second->genCode(out, allocator, tempIndex);
            int reg1 = allocator->loadToReg(tempIndex, false);
            reg = allocator->loadToReg(var_index, true);
            if(factors[i].first){
                out->text<<"mul $"<<reg<<" $"<<reg<<" $"<<reg1<<std::endl;
            }
            else {
                out->text<<"div $"<<reg<<" $"<<reg1<<std::endl;
                out->text<<"mflo $"<<reg<<std::endl;
            }
        }
    }
    allocator->releaseReg(tempIndex);
    return reg;
}

bool TermNode::returnConstant(int &val) {
    int ans = 1;
    for(auto x : factors){
        int p = 0;
        if(!x.second->returnConstant(p))return false;
        ans = x.first ? ans * p : ans / p;
    }
    val = ans;
    return true;
}

std::set<int> TermNode::getUseVarIndex() {
    std::set<int> ret;
    for (auto x : factors) {
        const std::set<int> &st = x.second->getFactorUseVarIndex();
        if(!st.empty())ret.insert(st.begin(), st.end());
    }
    return ret;
}

bool FactorNode::isReturnInt_Factor() {
    return isInt;
}

int FactorNode::genCode(Synthesizer *pSynthesizer, MemoryAllocator *pAllocator, int var_target) {
    int reg = pAllocator->loadToReg_writeOnly(var_target);
    pSynthesizer->text<<"li $"<<reg<<" , "<<val<<std::endl;
    return reg;
}

bool IdentiferFactor::isReturnInt_Factor() {
    ID_Type type = defineDat.type;
    return type == ID_ARR_2_I || type == ID_VAR_I || type == ID_ARR_I || type == ID_CNST_I;
}

bool IdentiferFactor::checkError(SymbolTable *symbolTable) {
    switch (dimension) {
        case 0: return true;
        case 1:
            if(!(dimension1->isReturnInt())){
                logError("char expression in array index");
                log_error2('i', getLine());   //数组元素的下标不能是字符型
                return false;
            }
            return true;
        case 2:
            if(!(dimension1->isReturnInt() && dimension2->isReturnInt())){
                logError("char expression in arr ay index");   //数组元素的下标不能是字符型
                log_error2('i', getLine());
                return false;
            }
            return true;
        default:return false;
    }
}

bool IdentiferFactor::returnConstant(int &val) {
    if(defineDat.type == ID_CNST_I || defineDat.type == ID_CNST_C){
        val = defineDat.cnstData;
        return true;
    }
    return false;
}

int IdentiferFactor::genCode(Synthesizer *out, MemoryAllocator *allocator, int var_index) {
    //TODO change var index ?
    int val = 0, ret;
    if(returnConstant(val)){
        int reg = allocator->loadToReg_writeOnly(var_index);
        out->text<<"li $"<<reg<<" , "<<val<<std::endl;
        ret = reg;
    }
    else if(isArr()){
        if(dimension == 1){
            if(dimension1->returnConstant(val))allocator->loadArrToReg(defineDat.uid, var_index, val* 4);
            else{
                dimension1->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->text<<"sll $"<<reg<<" $"<<reg<<" 2"<<std::endl;
                ret = allocator->loadArrToRegVarPos(defineDat.uid, var_index, varIndexTemp);
            }
        }
        if(dimension == 2){
            if(dimension1->returnConstant(val)){
                int val2;
                if(dimension2->returnConstant(val2))
                    ret = allocator->loadArrToReg(defineDat.uid, var_index, (defineDat.size[1] * val + val2) * 4);
                else {
                    dimension2->genCode(out, allocator, varIndexTemp);
                    int reg = allocator->loadToReg(varIndexTemp, true);
                    out->text<<"addi $"<<reg<<" $"<<reg<<" ,"<<defineDat.size[1] * val<<std::endl;
                    out->text<<"sll $" <<reg<<" $"<<reg<<" ,2"<<std::endl;
                    ret = allocator->loadArrToRegVarPos(defineDat.uid, var_index, varIndexTemp);
                }
            }
            else{
                int val2;
                if(dimension2->returnConstant(val2)) {
                    dimension1->genCode(out, allocator, varIndexTemp);
                    int reg = allocator->loadToReg(varIndexTemp, true);
                    out->multCnst(reg, reg, defineDat.size[1]* 4);
                    out->text<<"addi $"<<reg<<" $"<<reg<<" ,"<<val2 * 4<<std::endl;
                    ret = allocator->loadArrToRegVarPos(defineDat.uid, var_index, varIndexTemp);
                }else{
                    dimension1->genCode(out, allocator, varIndexTemp);
                    int reg = allocator->loadToReg(varIndexTemp, true);
                    out->multCnst(reg, reg, defineDat.size[1]);
                    dimension2->genCode(out, allocator, varIndexTemp2);
                    int reg2 = allocator->loadToReg(varIndexTemp, true);
                    reg = allocator->loadToReg(varIndexTemp2, false);
                    out->text<<"addu $"<<reg2<<" $"<<reg<<" $"<<reg2<<std::endl;
                    out->text<<"sll $" <<reg2<<" $"<<reg2<<" ,2"<<std::endl;
                    ret = allocator->loadArrToRegVarPos(defineDat.uid, var_index, varIndexTemp);
                }
            }
        }
    }else{
        allocator->move(defineDat.uid, var_index);
        return  0;
    }
    return ret;
}

void IdentiferFactor::codeSaveArr(Synthesizer *out, MemoryAllocator *allocator, int var_index) {
    int val = 0;
    if(dimension == 1){
        if(dimension1->returnConstant(val))allocator->saveArr(defineDat.uid, var_index, val* 4);
        else{
            dimension1->genCode(out, allocator, varIndexTemp);
            int reg = allocator->loadToReg(varIndexTemp, true);
            out->text<<"sll $"<<reg<<" $"<<reg<<" 2"<<std::endl;
            allocator->saveArrVarPos(defineDat.uid, var_index, varIndexTemp);
            allocator->releaseReg(varIndexTemp);
        }
    }
    if(dimension == 2){
        if(dimension1->returnConstant(val)){
            int val2;
            if(dimension2->returnConstant(val2))
                allocator->saveArr(defineDat.uid, var_index, (defineDat.size[1] * val + val2) * 4);
            else {
                dimension2->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->text<<"addi $"<<reg<<" $"<<reg<<" ,"<<defineDat.size[1] * val<<std::endl;
                out->text<<"sll $" <<reg<<" $"<<reg<<" ,2"<<std::endl;
                allocator->saveArrVarPos(defineDat.uid, var_index, varIndexTemp);
                allocator->releaseReg(varIndexTemp);
            }
        }
        else{
            int val2;
            if(dimension2->returnConstant(val2)) {
                dimension1->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->multCnst(reg, reg, defineDat.size[1]* 4);
                out->text<<"addi $"<<reg<<" $"<<reg<<" ,"<<val2 * 4<<std::endl;
                allocator->saveArrVarPos(defineDat.uid, var_index, varIndexTemp);
                allocator->releaseReg(varIndexTemp);
            }else{
                dimension1->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->multCnst(reg, reg, defineDat.size[1]);
                dimension2->genCode(out, allocator, varIndexTemp2);
                int reg2 = allocator->loadToReg(varIndexTemp, true);
                reg = allocator->loadToReg(varIndexTemp2, false);
                out->text<<"addu $"<<reg2<<" $"<<reg<<" $"<<reg2<<std::endl;
                out->text<<"sll $" <<reg2<<" $"<<reg2<<" ,2"<<std::endl;
                allocator->saveArrVarPos(defineDat.uid, var_index, varIndexTemp);
                allocator->releaseReg(varIndexTemp2);
                allocator->releaseReg(varIndexTemp);
            }
        }
    }

}

void IdentiferFactor::codeSaveCnst(Synthesizer *out, MemoryAllocator *allocator, int cnst) {
    int val = 0;
    if(dimension == 1){
        if(dimension1->returnConstant(val))allocator->saveArrCnst(defineDat.uid, cnst, val * 4);
        else{
            dimension1->genCode(out, allocator, varIndexTemp);
            int reg = allocator->loadToReg(varIndexTemp, true);
            out->text<<"sll $"<<reg<<" $"<<reg<<" 2"<<std::endl;
            allocator->saveArrCnstVarPos(defineDat.uid, cnst, varIndexTemp);
            allocator->releaseReg(varIndexTemp);
        }
    }
    if(dimension == 2){
        if(dimension1->returnConstant(val)){
            int val2;
            if(dimension2->returnConstant(val2))
                allocator->loadArrToReg(defineDat.uid, cnst, (defineDat.size[1] * val + val2) * 4);
            else {
                dimension2->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->text<<"addi $"<<reg<<" $"<<reg<<" ,"<<defineDat.size[1] * val<<std::endl;
                out->text<<"sll $" <<reg<<" $"<<reg<<" ,2"<<std::endl;
                allocator->saveArrCnstVarPos(defineDat.uid, cnst, varIndexTemp);
                allocator->releaseReg(varIndexTemp);
            }
        }
        else{
            int val2;
            if(dimension2->returnConstant(val2)) {
                dimension1->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->multCnst(reg, reg, defineDat.size[1]* 4);
                out->text<<"addi $"<<reg<<" $"<<reg<<" ,"<<val2 * 4<<std::endl;
                allocator->saveArrCnstVarPos(defineDat.uid, cnst, varIndexTemp);
            }else{
                dimension1->genCode(out, allocator, varIndexTemp);
                int reg = allocator->loadToReg(varIndexTemp, true);
                out->multCnst(reg, reg, defineDat.size[1]);
                dimension2->genCode(out, allocator, varIndexTemp2);
                int reg2 = allocator->loadToReg(varIndexTemp, true);
                reg = allocator->loadToReg(varIndexTemp2, false);
                out->text<<"addu $"<<reg2<<" $"<<reg<<" $"<<reg2<<std::endl;
                out->text<<"sll $" <<reg2<<" $"<<reg2<<" ,2"<<std::endl;
                allocator->saveArrCnstVarPos(defineDat.uid, cnst, varIndexTemp);
                allocator->releaseReg(varIndexTemp);
                allocator->releaseReg(varIndexTemp2);
            }
        }
    }
} // TODO: 重复 * 3 检查 & 结构优化
void FunctionCallFactor::addToTable(SymbolTable *table) {
    defindDat = table->getDiscriptions(callNode->name);
}
bool FunctionCallFactor::isReturnInt_Factor() {
    ID_Type type = defindDat.type;
    return type == ID_FUNC_I;
}

int FunctionCallFactor::genCode(Synthesizer *pSynthesizer, MemoryAllocator *pAllocator, int var_target) {
    callNode->genCode(pSynthesizer, pAllocator);
    int reg = pAllocator->loadToReg_writeOnly(var_target);
    pSynthesizer->text<<"move $"<<reg<<", $v0" <<std::endl;
    return reg;
}

std::set<int> FunctionCallFactor::getFactorUseVarIndex() {
    return callNode->getUseVarIndex();
}
